package frontend;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.HeadlessException;
import java.awt.Toolkit;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.JComponent;
import javax.swing.JFrame;

import backend.Answer;
import backend.Element;
import backend.Pipe;
import backend.InputTypes;
import backend.PipeDreamOutput;
import backend.Position;
import backend.Wall;


/*
 * This frame is the output used for the PipeDreamSolver. Draws in a window 
 * all the different pipes and the walls.
 * The constant CELL_SIZE sets the size of a square inside which the pipes
 * or walls will be drawn.
 * The constant TIMEINTERVAL sets the time between each draw and remove of the pipes
 * to be able to see the changes.
 */
public class Frame extends JFrame implements PipeDreamOutput{

	
	private static final int CELL_SIZE=48;
	private static final int TIMEINTERVAL = 100;
	private static final long serialVersionUID = 1L;
	private boolean drawable = true;
	private Map<Position,JComponent> map;  
	
	public Frame() throws HeadlessException {
		super("Pipe Dream Solver");
		setResizable(false);
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		map = new HashMap<Position,JComponent>();
	}
	
	/*
	 * Should be called to match the size of the map opened.
	 */
	public void changeSize(int rows, int cols){
		Dimension mapDim = new Dimension(cols * CELL_SIZE, rows * CELL_SIZE);
		Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
		setLocation((int)(screenDim.width/2 - mapDim.getWidth()/2), 
				(int)(screenDim.height/2 - mapDim.getHeight()/2));
		if(screenDim.height < (mapDim.height+CELL_SIZE/3)  || screenDim.width < (mapDim.width +4)){
			drawable = false;
		}else{
			setSize((int)mapDim.getWidth(),(int)mapDim.getHeight()+29);
		}
		this.validate();
	}
	
	public void drawStart(Position P, InputTypes dir){
		JComponent comp = new StartPipe(P, dir);
		this.add(comp);
		map.put(P, comp);
		this.validate();
	}
	
	private void drawPipe(Position P, InputTypes from, InputTypes to){
		JComponent comp = new Pipe(P, from, to);
		this.add(comp);
		map.put(P, comp);
		this.validate();
	}
	
	public  void drawWall(Position P){
		JComponent comp = new BlackBlock(P);
		this.add(comp);
		map.put(P, comp);
		this.validate();
	}
	
	private void drawCross(Position P){
		JComponent comp = new CrossPipe(P);
		this.add(comp);
		map.put(P, comp);
		this.validate();
	}
	
	/*
	 * The map is drawable if the map fits the actual screen.
	 * Depends on the CELL_SIZE and dimensions of the map.
	 */
	public boolean isDrawable(){
		return drawable;
	}
	
	
	private void drawElement(Position p, Element elem) {
		if(elem instanceof Wall){
			drawWall(p);
		}else if(elem instanceof backend.Pipe){
			if(((backend.Pipe)elem).isCrossPipe()){
				drawCross(p);
			}else if(elem.getDirections().size() == 1){
				drawStart(p, elem.getDirections().get(0));
			}
			else if(elem.getDirections().size() == 2){
				drawPipe(p, elem.getDirections().get(0), elem.getDirections().get(1));
			}
		}
	}

	private void drawPath(Answer ans) {
		if(ans == null){
			return ;
		}
		HashMap<Position, backend.Pipe> pipes = ans.getPipes();
		for(Map.Entry<Position,backend.Pipe> aux: pipes.entrySet()){
			drawElement(aux.getKey(), aux.getValue());
		}
	}

	private static abstract class Block extends JComponent {
		private static final long serialVersionUID = 1L;
		protected Position P;
		protected InputTypes[] fromTo;
				
		protected Block(Position p){
			this(p, null, null);
		}
		
		protected Block(Position p, InputTypes to){
			this(p, to, to);
		}
		
		protected Block(Position p, InputTypes from, InputTypes to){
			P = p;
			fromTo = new InputTypes[2];
			this.fromTo[0] = from;
			this.fromTo[1] = to;
		}
		
		/*  
		 * Draws CELL_SIZE*CELL_SIZE square in the position P. After that, calls the abstract
		 * method for drawing the rest of the block, which must be defined on each
		 * extending class.
		 */
		@Override
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);
			g.setColor(Color.GRAY);
			g.drawRect(P.getY()*CELL_SIZE, P.getX()*CELL_SIZE, CELL_SIZE, CELL_SIZE);
			g.setColor(Color.BLUE);
			draw(g);
		}
		
		protected abstract void draw(Graphics g);
	}
	
	private static class Pipe extends Block {
		private static final long serialVersionUID = 1L;

		protected Pipe(Position p, InputTypes from, InputTypes to) {
			super(p, from, to);
		}

		@Override
		protected void draw(Graphics g){
			for(InputTypes dir: fromTo){		
				switch(dir){
				case S:
					g.drawRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE+CELL_SIZE/3, CELL_SIZE/3, 2*(CELL_SIZE/3));
					g.fillRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE+CELL_SIZE/3, CELL_SIZE/3, 2*(CELL_SIZE/3));
					break;
				case W:
					g.drawRect(P.getY()*CELL_SIZE, P.getX()*CELL_SIZE+CELL_SIZE/3, 2*CELL_SIZE/3, (CELL_SIZE/3));
					g.fillRect(P.getY()*CELL_SIZE, P.getX()*CELL_SIZE+CELL_SIZE/3, 2*CELL_SIZE/3, (CELL_SIZE/3));
					break;
				case E:
					g.drawRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE+CELL_SIZE/3, 2*CELL_SIZE/3, (CELL_SIZE/3));
					g.fillRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE+CELL_SIZE/3, 2*CELL_SIZE/3, (CELL_SIZE/3));
					break;
				case N:
					
					g.drawRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE, CELL_SIZE/3, 2*(CELL_SIZE/3));
					g.fillRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE, CELL_SIZE/3, 2*(CELL_SIZE/3));
					break;
				}
			}
		}
	}
	
	private static class CrossPipe extends Pipe{

		protected CrossPipe(Position p) {
			super(p,null,null);
			P = p;
			fromTo = new InputTypes[4];
			this.fromTo[0] = InputTypes.W;
			this.fromTo[1] = InputTypes.E;
			this.fromTo[2] = InputTypes.N;
			this.fromTo[3] = InputTypes.S;
		}

		private static final long serialVersionUID = 1L;
		
	}
	
	private static class StartPipe extends Block{
		private static final long serialVersionUID = 2L;

		protected StartPipe(Position p, InputTypes to) {
			super(p, to);
		}

		@Override
		protected void draw(Graphics g){
			switch(fromTo[0]){
			case S:
				g.drawRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE+CELL_SIZE/3, CELL_SIZE/3, 2*(CELL_SIZE/3));
				g.fillRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE+CELL_SIZE/3, CELL_SIZE/3, 2*(CELL_SIZE/3));
				break;
			case W:
				g.drawRect(P.getY()*CELL_SIZE, P.getX()*CELL_SIZE+CELL_SIZE/3, 2*CELL_SIZE/3, (CELL_SIZE/3));
				g.fillRect(P.getY()*CELL_SIZE, P.getX()*CELL_SIZE+CELL_SIZE/3, 2*CELL_SIZE/3, (CELL_SIZE/3));
				break;
			case E:
				g.drawRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE+CELL_SIZE/3, 2*CELL_SIZE/3, (CELL_SIZE/3));
				g.fillRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE+CELL_SIZE/3, 2*CELL_SIZE/3, (CELL_SIZE/3));
				break;
			case N:
				
				g.drawRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE, CELL_SIZE/3, 2*(CELL_SIZE/3));
				g.fillRect(P.getY()*CELL_SIZE+CELL_SIZE/3, P.getX()*CELL_SIZE, CELL_SIZE/3, 2*(CELL_SIZE/3));
				break;
			}
			g.setColor(Color.GREEN);
			g.drawRect(P.getY()*CELL_SIZE+CELL_SIZE/3+3, P.getX()*CELL_SIZE+CELL_SIZE/3+3, CELL_SIZE/4-3, CELL_SIZE/4-3);
			g.fillRect(P.getY()*CELL_SIZE+CELL_SIZE/3+3, P.getX()*CELL_SIZE+CELL_SIZE/3+3, CELL_SIZE/4-3, CELL_SIZE/4-3);
			g.setColor(Color.BLUE);
		}
	}
	
	private static class BlackBlock extends Block{
		private static final long serialVersionUID = 1L;

		protected BlackBlock(Position p) {
			super(p);
		}

		@Override
		protected void draw(Graphics g) {
			g.setColor(Color.GRAY);
			g.fillRect(P.getY()*CELL_SIZE, P.getX()*CELL_SIZE, CELL_SIZE, CELL_SIZE);
		}
	}

	@Override
	public void addPipe(Position p, List<InputTypes> directions) {
		if(directions.size() == 4){
			if(map.containsKey(p)){
				if(map.get(p) instanceof CrossPipe){
					return ;
				}
			}
			drawCross(p);
			
		}else if(directions.size()==1){
			//start pipe
		}
		else if(directions.size() == 2){
			drawPipe(p, directions.get(0), directions.get(1));
		}	
		try {
			Thread.sleep(TIMEINTERVAL /2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}


	@Override
	public void removePipe(Position p) {
		JComponent comp = map.get(p);
		this.remove(comp);
		this.validate();
		try {
			Thread.sleep(TIMEINTERVAL / 2);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}		
	
	@Override
	public void printSolution(Map<Position, backend.Pipe> pipes) {
		for(Entry<Position,backend.Pipe> e: pipes.entrySet()){
			drawElement(e.getKey(), e.getValue());
		}
		return ;
	}
}
